class BlackDeathPawn extends UDKPawn//GamePawn
	abstract;

//state node
var AnimNode_MultiBlendPerBone StateNode;

//The socket that weapons attach to
var name WeaponSocket;

//Wiether or not pawn is currently invisible
var bool isHidden;

/** The pawn's light environment */
var DynamicLightEnvironmentComponent LightEnvironment;

//Sounds
var class<BD_PawnSoundGroup> SoundGroupClass;
/** Max distance from listener to play footstep sounds */
var float MaxFootstepDistSq;

/** Max distance from listener to play jump/land sounds */
var float MaxJumpSoundDistSq;

var bool bOnFire;
//How much damage being lit of fire does per second
var int FireDamage;
var Name fireSocket;
var ParticleSystemComponent FireParticleEffectComponent;

/** Initial inventory */
var() array< class<Inventory> >	InitialInventory;

//Assigns enumeration to hold the index value of animation sequences
enum enum_animation { equip_sword, attack, idle, unequip_torch, equip_torch, unequip_sword };

//Animation speed change variables -- used to pass parameters to code on the tick
var bool bScalingAnim;
var float animRateScaleEaseOutTime;
var float animScaleChangeTime;
var float animScaleChangeTimeRemaining;
var float animScaleTargetValue;
var float varAnimValueDiff;
var float animScaleTarget;

var() SoundCue DamageSound;

//URGENT: CHANGE THE VISIBILTY MODIFY TO USE THE DEFULT BLACKDEATH WEAPON CLASS WHEN IT EXISTS!!!!!
//END VARIBLES


simulated function PostBeginPlay()
{
	super.PostBeginPlay();
	SetMovementPhysics();
}

/*This Function is called after the AnimTre is initialized.
*Any nodes that we want to control code, we should look up here.
*/
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
	//`log("postinit anim tree");
	super.PostInitAnimTree(SkelComp);
	if (SkelComp == Mesh)
	{
		StateNode = AnimNode_MultiBlendPerBone(Mesh.FindAnimNode('State'));
	}
}
simulated function changeState(int StateIndex, float BlendTime)
{
	ResetMasks(BlendTime);
	if(stateIndex != 0)
	{
		StateNode.SetMaskWeight(StateIndex-1, 1.0, BlendTime);
	}
}

simulated function ResetMasks(float BlendTime)
{
	local int len;
	local int i;
	len = StateNode.children.length-1;
	i = -1;
	while(++i < len)
	{
		StateNode.SetMaskWeight(i, 0.0, BlendTime);
	}
}
	
//set up some holders here for functions that extending classses have
simulated function LightSwing()
{
}

simulated function HeavySwing()
{
}
simulated function EndSwing()
{
}

function AddDefaultInventory()
{
	local int i;
	for (i=0; i < InitialInventory.Length; i++)
	{
		CreateInventory(InitialInventory[i]);
		`log(Self@": Created inventory item "@InitialInventory[i]);
	}
	Controller.SwitchToBestWeapon(true);
}

function stagger()
{
	if(canBeStaggered())
	gotostate('staggered');
}

function bool canBeStaggered()
{
	return true;
}

simulated function StaggerRecover()
{
	gotostate('');
}

State Staggered
{
	event BeginState(Name PreviousStateName)
	{
		setTimer(1.0, false, 'StaggerRecover');
	}
}

simulated function ChangeVisibility(bool bIsVisible)
{
	//`log("Change visibility to: "@!bIsVisible);
	if (Mesh != None)
	{
		mesh.SetHidden(!bIsVisible);
		
	}
	if(BlackDeathInventoryManager(InvManager) != none)
	{
		BlackDeathInventoryManager(InvManager).SetItemsHidden(bIsVisible);
	}
	isHidden = !bIsVisible;
}

//Tell the game to use the third person camera always
simulated function name GetDefaultCameraMode(PlayerController RequestedBy)
{
	return 'FollowCam';
	//return 'ThirdPerson';
}

simulated function Ignite()
{
	if(!bOnFire)
	{
		Mesh.AttachComponentToSocket(FireParticleEffectComponent, fireSocket);
		//`log("igniteing");
		SetTimer(1.0, true, 'DealFireDamage');
		SetTimer(0.5, true, 'IgniteNearbyPawns');
		bOnFire = true;
	}
}

simulated function IgniteNearbyPawns()
{
	local BlackDeathPawn BDP;
	ForEach OverlappingActors(class'BlackDeathPawn', BDP, 20, Location)
	{
		if(BDP != self)
		{
			if(RandRange(0.0, 1.0) > 0.5)
			{
				BDP.ignite();
			}
		}
	}
}

simulated function Extinguish()
{
	if(bOnFire)
	{
		Mesh.DetachComponent(FireParticleEffectComponent);
		ClearTimer('DealFireDamage');
		ClearTimer('IgniteNearbyPawns');
		bOnFire = false;
	}
}

simulated function DealFireDamage()
{
	TakeDamage(FireDamage, None, Location, vect(0,0,0), class'UTDmgType_Fire');
	//`log("Enemy Health now"@Health);
}

function PlayHit(float Damage, Controller InstigatedBy, vector HitLocation, class<DamageType> damageType, vector Momentum, TraceHitInfo HitInfo)
{
	if ( (Damage <= 1) && ((Controller == None) || !Controller.bGodMode) )
		return;

	LastPainTime = WorldInfo.TimeSeconds;
	if(damageType != class'UTDmgType_Fire')
		PlaySound(DamageSound);
}

auto state Default
{
}

function AnimAttachWeaponToHand() //Socket current weapon to hand, if invnetory is socketed then calls remove from holder function to notify invnetory of change
{
	local BlackDeathSocketedInventoryManager BDSIM;
	local BlackDeathWeapon BDW;
	BDW = BlackDeathWeapon(Weapon);
	if(BDW != none)
	{
		BDSIM = BlackDeathSocketedInventoryManager(InvManager);
		if(BDSIM != none)
		{
			BDSIM.RemoveFromHolder(BDW);
		}
		BDW.AttachWeaponTo( Mesh, WeaponSocket);
	}
}

function AnimRemoveWeaponFromHand() //Removes weapon from hand socket, If inventory is socketed then calls a place in holder function to tell the invnetory manager to socket the weapon to a holder
{
	local BlackDeathSocketedInventoryManager BDSIM;
	local BlackDeathWeapon BDW;
	BDW = BlackDeathWeapon(Weapon);
	if(BDW != none)
	{
		BDSIM = BlackDeathSocketedInventoryManager(InvManager);
		if(BDSIM != none)
		{
			BDSIM.PlaceInHolder(BDW);
		}
		else //if its not going in a holder then just remove it.
		{
			BDW.DetachWeapon(); //make sure this works later
		}
	}
}

simulated function Tick(float DeltaTime)
{
	//SetLookAt();
	
	if(bScalingAnim)
	{
		//WorldInfo.Game.Broadcast(None, "Called tick stuff");
		
		Mesh.GlobalAnimRateScale += varAnimValueDiff * (deltaTime/animScaleChangeTime);
		animScaleChangeTimeRemaining -= deltaTime;
		
		//if we've reached our goal or time is up, set the animratescale to the target and stop
		if(animScaleChangeTimeRemaining <= 0 || 
			( varAnimValueDiff < 0 && Mesh.GlobalAnimRateScale < animScaleTarget) ||
			( varAnimValueDiff >= 0 && Mesh.GlobalAnimRateScale >= animScaleTarget) )
		{
			Mesh.GlobalAnimRateScale = animScaleTarget;
			//WorldInfo.Game.Broadcast(None, "Ending anim speed change at "@Mesh.GlobalAnimRateScale);
			//WorldInfo.Game.Broadcast(None, "AnimScale setting is: "@Mesh.GlobalAnimRateScale);
			bScalingAnim = false;
		}
		//WorldInfo.Game.Broadcast(None, "animScaleChangeTime is: "@Mesh.GlobalAnimRateScale);	
	}
}
 

/**This function changes the value of a the Pawn's global anim rate over time.*/
function scalePawnAnimRate(float targetScale, float rate)
{
	animScaleTarget = targetScale;
	varAnimValueDiff = targetScale - Mesh.GlobalAnimRateScale;
	animScaleTargetValue = targetScale;
	animScaleChangeTime = rate;
	animScaleChangeTimeRemaining = rate;
	bScalingAnim = true;
}

/**Adjust global animation speed
 * Parameters: animScaler: Scale animation to eg 0.5 for half speed
 *             duration:   Maintain this scale for [duration] seconds before restoring to 1.0  */
function setPawnAminScale(float animScaler, float duration, optional float easeInTime = 0, optional float easeOutTime = 0)
{
	Mesh.GlobalAnimRateScale = 1.0;
	ClearTimer('animScaleTimer');
	scalePawnAnimRate(animScaler, easeInTime);
	animRateScaleEaseOutTime = easeOutTime;
	SetTimer(duration + easeInTime, false, 'animScaleTimer');
}

function animScaleTimer()
{
	scalePawnAnimRate(1.0, animRateScaleEaseOutTime);
	WorldInfo.Game.Broadcast(None, "AnimScale reset!");
}

//handelers for script notifications called during equip/unequip anims.
function FinishEquip()
{
	local BlackDeathWeapon BDW;
	BDW = BlackDeathWeapon(Weapon);
	if(BDW != none)
	{
		BDW.FinishEquip();
	}
}

function FinishUnequip()
{
	local BlackDeathWeapon BDW;
	BDW = BlackDeathWeapon(Weapon);
	if(BDW != none)
	{
		BDW.FinishUnequip();
	}
}

/** placeholders that allow us to call functions on that we later immpliment for each type of pawn. (polymorphisum FTW!) */
simulated function PlayAnim( enum_animation animation )
{
}

simulated function PlayWeaponEquip()
{
}

simulated function PlayWeaponPutDown()
{
}

simulated function PlayWeaponSwing()
{
}

simulated function NotifyAttacking()
{
}

simulated function NotifyEndAttaking()
{
}

simulated function NotifyPawnWeaponActive()
{
}

simulated function NotifyPawnTorchActive()
{
}

/** called during swing anim to tell weapon that it needs to calc damage */
function CallAttack()
{
	local BlackDeathWeapon BDW;
	BDW = BlackDeathWeapon(Weapon);
	if(BDW != none)
	{
		BDW.CallAttack();
	}
}

simulated event PlayFootStepSound(int FootDown)
{
	local PlayerController PC;

	if ( !IsFirstPerson() )
	{
		ForEach LocalPlayerControllers(class'PlayerController', PC)
		{
			if ( (PC.ViewTarget != None) && (VSizeSq(PC.ViewTarget.Location - Location) < MaxFootstepDistSq) )
			{
				ActuallyPlayFootstepSound(FootDown);
				return;
			}
		}
	}
}

simulated function ActuallyPlayFootstepSound(int FootDown)
{
	local SoundCue FootSound;

	FootSound = SoundGroupClass.static.GetFootstepSound(FootDown, GetMaterialBelowFeet());
	if (FootSound != None)
	{
		PlaySound(FootSound, false, true,,, true);
	}
}

simulated function name GetMaterialBelowFeet()
{
	local vector HitLocation, HitNormal;
	local TraceHitInfo HitInfo;
	local UTPhysicalMaterialProperty PhysicalProperty;
	local actor HitActor;
	local float TraceDist;

	TraceDist = 1.5 * GetCollisionHeight();

	HitActor = Trace(HitLocation, HitNormal, Location - TraceDist*vect(0,0,1), Location, false,, HitInfo, TRACEFLAG_PhysicsVolumes);
	if ( WaterVolume(HitActor) != None )
	{
		return (Location.Z - HitLocation.Z < 0.33*TraceDist) ? 'Water' : 'ShallowWater';
	}
	if (HitInfo.PhysMaterial != None)
	{
		PhysicalProperty = UTPhysicalMaterialProperty(HitInfo.PhysMaterial.GetPhysicalMaterialProperty(class'UTPhysicalMaterialProperty'));
		if (PhysicalProperty != None)
		{
			return PhysicalProperty.MaterialType;
		}
	}
	return '';

}

simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
  local vector ApplyImpulse, ShotDir;
  Extinguish();
  
  bReplicateMovement = false;
  bTearOff = true;
  Velocity += TearOffMomentum;
  SetDyingPhysics();
  bPlayedDeath = true;
  HitDamageType = DamageType; // these are replicated to other clients
  TakeHitLocation = HitLoc;
  if ( WorldInfo.NetMode == NM_DedicatedServer )
  {
    GotoState('Dying');
    return;
  }
  InitRagdoll();
  mesh.MinDistFactorForKinematicUpdate = 0.f;
  
  if (Physics == PHYS_RigidBody)
  {
    //@note: Falling instead of None so Velocity/Acceleration don't get cleared
    setPhysics(PHYS_Falling);
  }
  PreRagdollCollisionComponent = CollisionComponent;
  CollisionComponent = Mesh;
  if( Mesh.bNotUpdatingKinematicDueToDistance )
  {
    Mesh.ForceSkelUpdate();
    Mesh.UpdateRBBonesFromSpaceBases(TRUE, TRUE);
  }
  if( Mesh.PhysicsAssetInstance != None )
    Mesh.PhysicsAssetInstance.SetAllBodiesFixed(FALSE);
  Mesh.SetRBChannel(RBCC_Pawn);
  Mesh.SetRBCollidesWithChannel(RBCC_Default,TRUE);
  Mesh.SetRBCollidesWithChannel(RBCC_Pawn,TRUE);
  Mesh.SetRBCollidesWithChannel(RBCC_Vehicle,TRUE);
  Mesh.SetRBCollidesWithChannel(RBCC_Untitled3,FALSE);
  Mesh.SetRBCollidesWithChannel(RBCC_BlockingVolume,TRUE);
  Mesh.ForceSkelUpdate();
  Mesh.UpdateRBBonesFromSpaceBases(TRUE, TRUE);
  Mesh.PhysicsWeight = 1.0;
  Mesh.bUpdateKinematicBonesFromAnimation=false;
  // mesh.bPauseAnims=True;
  Mesh.SetRBLinearVelocity(Velocity, false);
  mesh.SetTranslation(vect(0,0,1) * 6);
  Mesh.ScriptRigidBodyCollisionThreshold = MaxFallSpeed;
  Mesh.SetNotifyRigidBodyCollision(true);
  Mesh.WakeRigidBody();
  if( TearOffMomentum != vect(0,0,0) )
  {
    ShotDir = normal(TearOffMomentum);
    ApplyImpulse = ShotDir * DamageType.default.KDamageImpulse;
    // If not moving downwards - give extra upward kick
    if ( Velocity.Z > -10 )
    {
      ApplyImpulse += Vect(0,0,1)*2;
    }
    Mesh.AddImpulse(ApplyImpulse, TakeHitLocation,, true);
  }
  GotoState('Dying');
}

DefaultProperties
{
	SoundGroupClass=class'BD_PawnSoundGroup'
	MaxFootstepDistSq=9000000.0
	MaxJumpSoundDistSq=16000000.0
	InventoryManagerClass=class'BlackDeathInventoryManager'
	ControllerClass=class'BlackDeathEnemyController'
	Components.Remove(Sprite)

//Setting up the light environment
    Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
        ModShadowFadeoutTime=0.25
        MinTimeBetweenFullUpdates=0.2
        AmbientGlow=(R=.01,G=.01,B=.01,A=1)
        AmbientShadowColor=(R=0.15,G=0.15,B=0.15)
        bSynthesizeSHLight=TRUE
    End Object
    Components.Add(MyLightEnvironment)
	LightEnvironment=MyLightEnvironment
//Setting up the mesh and animset components
    Begin Object Class=SkeletalMeshComponent Name=InitialSkeletalMesh
        CastShadow=true
        bCastDynamicShadow=true
        bOwnerNoSee=false
        LightEnvironment=MyLightEnvironment;
		BlockRigidBody=true;
		CollideActors=true;
		BlockZeroExtent=true;
//What to change if you'd like to use your own meshes and animations
        PhysicsAsset=PhysicsAsset'BD_CHARACTERS.Mesh.Vincent_Physics'
        AnimSets(0)=AnimSet'BD_CHARACTERS.Animation.VincentMoveSet'
        AnimTreeTemplate=AnimTree'BD_CHARACTERS.Animation.Human_AnimTree'
        SkeletalMesh=SkeletalMesh'BD_CHARACTERS.Mesh.Vincent'
    End Object
//Setting up a proper collision cylinder
    Mesh=InitialSkeletalMesh;
    Components.Add(InitialSkeletalMesh); 
	CollisionType=COLLIDE_BlockAll
	Begin Object Name=CollisionCylinder
		CollisionRadius=+0024.000000
		CollisionHeight=+0044.000000
	End Object
	CylinderComponent=CollisionCylinder
	
	//Temp fix, creat fire effects here and apply when neccisary
	Begin Object Class=ParticleSystemComponent Name=FireEffect
		bAutoActivate=TRUE
		Template=ParticleSystem'Victim_FX_01.Particles.LoopingFire'
		Scale3D=(X=0.5,Y=0.5,Z=0.5)
		//Translation=(X=0.0,Y=0.0,Z=-44.0)
		SecondsBeforeInactive=1.0f
	End Object
	FireParticleEffectComponent=FireEffect
	
	HealthMax = 100
	FireDamage = 5;
	
	
	isHidden = false;
	bOnFire = false;
	
	AirSpeed=150.000000
	GroundSpeed=150.000000
	bCanCrouch=False
	bCanClimbLadders=False
	bCanPickupInventory=False
	bCanBeDamaged=true
	bCanSwim=true

	DamageSound=SoundCue'BD_SFX.Enemy.Blunt_hit'
	
	jumpz = 250;
	
	WeaponSocket=WeaponPoint
	fireSocket=FireEffectSocket
}